Import cal3d

Class CalBone

	Field m_coreBone:CalCoreBone
	Field m_skeleton:CalSkeleton
	Field m_accumulatedWeight:Float
	Field m_accumulatedWeightAbsolute:Float
	Field m_translation:CalVector
	Field m_rotation:CalQuaternion
	Field m_translationAbsolute:CalVector
	Field m_rotationAbsolute:CalQuaternion
	Field m_translationBoneSpace:CalVector
	Field m_rotationBoneSpace:CalQuaternion
	Field m_transformMatrix:CalMatrix
	Field m_boundingBox:CalBoundingBox
	
	Method New()
		 m_coreBone = Null
		 m_skeleton = Null
		 m_accumulatedWeight = 0
		 m_accumulatedWeightAbsolute = 0
		 m_translation = New CalVector
		 m_rotation = New CalQuaternion
		 m_translationAbsolute = New CalVector
		 m_rotationAbsolute = New CalQuaternion
		 m_translationBoneSpace = New CalVector
		 m_rotationBoneSpace = New CalQuaternion
		 m_transformMatrix = New CalMatrix
		 m_boundingBox = New CalBoundingBox	
	End
	
	
	Method New(coreBone:CalCoreBone)

		 m_coreBone = coreBone
		 m_skeleton = Null
		 m_accumulatedWeight = 0
		 m_accumulatedWeightAbsolute = 0
		 m_translation = New CalVector
		 m_rotation = New CalQuaternion
		 m_translationAbsolute = New CalVector
		 m_rotationAbsolute = New CalQuaternion
		 m_translationBoneSpace = New CalVector
		 m_rotationBoneSpace = New CalQuaternion
		 m_transformMatrix = New CalMatrix
		 m_boundingBox = New CalBoundingBox
		
		clearState()
	End


	Method blendState:Void(weight:Float, translation:CalVector, rotation:CalQuaternion)
		If m_accumulatedWeightAbsolute = 0 then
			' it is the first state, so just copy it into the bone state
			m_translationAbsolute.assign(translation)
			m_rotationAbsolute.assign(rotation)
			m_accumulatedWeightAbsolute = weight
		
		Else 
			' it is not the first state, so blend all attributes
			Local factor:Float = weight / (m_accumulatedWeightAbsolute + weight)
	
			m_translationAbsolute.blend(factor, translation)
			m_rotationAbsolute.blend(factor, rotation)
			m_accumulatedWeightAbsolute += weight
		Endif
		
	End
	
	Method calculateState:Void()
		' check if the bone was not touched by any active animation
		If m_accumulatedWeight = 0 then
			' set the bone to the initial skeleton state
			m_translation.assign(m_coreBone.getTranslation())
			m_rotation.assign(m_coreBone.getRotation())
		Endif
	
		' get parent bone id
		Local parentId:Int = m_coreBone.getParentId()
	
		If parentId = -1 then
			' no parent, this means absolute state == relative state
			m_translationAbsolute.assign(m_translation)
			m_rotationAbsolute.assign(m_rotation)
		
		Else 
			' get the parent bone
			Local parent:CalBone = m_skeleton.getBone(parentId)
	
			' transform relative state with the absolute state of the parent
			m_translationAbsolute.assign(m_translation)
			m_translationAbsolute.multQuaternionLocal(parent.getRotationAbsolute())
			m_translationAbsolute.addLocal(parent.getTranslationAbsolute())
	
			m_rotationAbsolute.assign(m_rotation)
			m_rotationAbsolute.multQuaternionLocal(parent.getRotationAbsolute())
		Endif
	
		' calculate the bone space transformation
		m_translationBoneSpace.assign(m_coreBone.getTranslationBoneSpace())
		m_translationBoneSpace.multQuaternionLocal(m_rotationAbsolute)
		m_translationBoneSpace.addLocal(m_translationAbsolute)
	
		m_rotationBoneSpace.assign(m_coreBone.getRotationBoneSpace())
		m_rotationBoneSpace.multQuaternionLocal(m_rotationAbsolute)
	
		' Generate the vertex transform.  
		'TODO: If I ever add support for bone-scaling to Cal3D, this step will become significantly more complex.
		m_transformMatrix.assign(m_rotationBoneSpace)
	
		' calculate all child bones
		Local listChildId:Int[] = m_coreBone.getListChildId()
		For Local iChildId:Int = 0 To listChildId.Length-1
			m_skeleton.getBone(listChildId[iChildId]).calculateState()
		Next	
	End
	
	Method clearState:Void()
		m_accumulatedWeight = 0
		m_accumulatedWeightAbsolute = 0
	End
	
	Method getCoreBone:CalCoreBone()
		Return m_coreBone
	End
	
	Method setCoreState:Void()
		' set the bone to the initial skeleton state
		m_translation.assign(m_coreBone.getTranslation())
		m_rotation.assign(m_coreBone.getRotation())
	
		' set the appropriate weights
		m_accumulatedWeightAbsolute = 1
		m_accumulatedWeight = 1
	
		calculateState()		
	End
	
	Method setCoreStateRecursive:Void()
		' set the bone to the initial skeleton state
		m_translation.assign(m_coreBone.getTranslation())
		m_rotation.assign(m_coreBone.getRotation())
	
		' set the appropriate weights
		m_accumulatedWeightAbsolute = 1
		m_accumulatedWeight = 1
	
		' set core state for all child bones
		Local listChildId:Int = m_coreBone.getListChildId()
		For Local iChildId:Int = 0 To listChildId.Length-1
			m_skeleton.getBone(listChildId[iChildId]).setCoreStateRecursive()
		Next
	
		calculateState() 		
	End
	
	Method setRotation:Void(rotation:CalQuaternion)
		m_rotation.assign(rotation)
		m_accumulatedWeightAbsolute = 1
		m_accumulatedWeight = 1		
	End
	
	Method getRotation:CalQuaternion()
		Return m_rotation
	End
	
	Method getRotationAbsolute:CalQuaternion()
		Return m_rotationAbsolute
	End
	
	Method getRotationBoneSpace:CalQuaternion()
		Return m_rotationBoneSpace
	End
	
	Method setTranslation:Void(translation:CalVector)
		m_translation.assign(translation)
		m_accumulatedWeightAbsolute = 1
		m_accumulatedWeight = 1		
	End
	
	Method getTranslation:CalVector()
		Return m_translation
	End
	
	Method getTranslationAbsolute:CalVector()
		Return m_translationAbsolute
	End	

	Method getTranslationBoneSpace:CalVector()
		Return m_translationBoneSpace
	End
	
	Method getTransformMatrix:CalMatrix()
		Return m_transformMatrix
	End	
	
	Method lockState:Void()
		' clamp accumulated weight
		If m_accumulatedWeightAbsolute > 1 - m_accumulatedWeight then
			m_accumulatedWeightAbsolute = 1 - m_accumulatedWeight
		Endif
	
		If m_accumulatedWeightAbsolute > 0 then
			If m_accumulatedWeight = 0 then
				' it is the first state, so just copy it into the bone state
				m_translation.assign(m_translationAbsolute)
				m_rotation.assign(m_rotationAbsolute)
				m_accumulatedWeight = m_accumulatedWeightAbsolute
			
			else 
				' it is not the first state, so blend all attributes
				Local factor:Float = m_accumulatedWeightAbsolute / (m_accumulatedWeight + m_accumulatedWeightAbsolute)
	
				m_translation.blend(factor, m_translationAbsolute)
				m_rotation.blend(factor, m_rotationAbsolute)
				m_accumulatedWeight += m_accumulatedWeightAbsolute
			Endif
	
			m_accumulatedWeightAbsolute = 0
		Endif		
	End
	
	Method setSkeleton:Void(skeleton:CalSkeleton)
		m_skeleton = skeleton
	End


	Method calculateBoundingBox:Void()
		If this.getCoreBone().isBoundingBoxPrecomputed() = False then
			Return
		Endif
		Local dir:CalVector = New CalVector(1, 0, 0)
		dir.multMatrixLocal(this.getTransformMatrix())
		this.m_boundingBox.plane[0].setNormal(dir)
	
		dir.assign(-1, 0, 0)
		dir.multMatrixLocal(this.getTransformMatrix())
		this.m_boundingBox.plane[1].setNormal(dir)
	
		dir.assign(0, 1, 0)
		dir.multMatrixLocal(this.getTransformMatrix())
		this.m_boundingBox.plane[2].setNormal(dir)
	
		dir.assign(0, -1, 0)
		dir.multMatrixLocal(this.getTransformMatrix())
		this.m_boundingBox.plane[3].setNormal(dir)
	
		dir.assign(0, 0, 1)
		dir.multMatrixLocal(this.getTransformMatrix())
		this.m_boundingBox.plane[4].setNormal(dir)
	
		dir.assign(0, 0, -1)
		dir.multMatrixLocal(this.getTransformMatrix())
		this.m_boundingBox.plane[5].setNormal(dir)
	
		Local position:CalVector = New CalVector
		For Local i:Int = 0 To 5
			this.getCoreBone().getBoundingData(i, position)
	
			position.multMatrixLocal(this.getTransformMatrix())
			position.addLocal(this.getTranslationBoneSpace())
	
			For Local planeId:Int = 0 To 5
				If this.m_boundingBox.plane[planeId].eval(position) < 0 Then
					this.m_boundingBox.plane[planeId].setPosition(position)
				Endif
			Next
		Next	
	End
	
	Method getBoundingBox:CalBoundingBox()
		Return m_boundingBox
	End

End Class